
"""
created by Cheka, 2010-6-27, at WSU, Pullman, WA
after comparision between Python and F#, finally I choose Python again
because I really cannot get used to the strange syntax of F# and that strange Functional Programming Style

More important is that Python obeys such a philosophy that "There is only one best method to do a single thing well"
and another point is I think that in .NET world, C# is just enough for me

Dear Python, I am coming back !!!
"""

import unittest
import random
import math
import cmath

class TestNumerics(unittest.TestCase):
    """
    test basic syntax about numerics
    """
    
    def test_convert_from_string(self):
        assert 9 == int("9")
        
        self.assertRaises(ValueError,lambda : float("not number"))
        
    def test_convert_to_string(self):
        self.assertEqual("9",str(9))
        self.assertEqual("3.14",str(3.14))
        
    def test_conjunction_operators(self):
        assert (2 < 4) and (5 > 1)
        assert 2 < 3 < 4 # a simple way to write "2 < 3 and 3 < 4"
        
    def test_types_typeis(self):
        """
        python don't have many numeric types
        only types includes: int, long, bool, float, complex
        """
        self.assertTrue(type(5) is int)
        self.assertTrue(type(100l) is long)
        self.assertTrue(type(1+2j) is complex)
        
    def test_types_isinstance(self):
        self.assertTrue(isinstance(5,int))
        self.assertTrue(isinstance(5l,long))
        self.assertTrue(isinstance(3.14,float))
        self.assertTrue(isinstance(True,bool))
        
    def test_math(self):
        self.assertEqual(1,3/2)
        self.assertEqual(1,3//2)
        
        self.assertAlmostEqual(1.5,float(3)/2)
        
    def test_bool(self):
        """
        all empty containers and number zero are false
        """
        self.assertFalse(bool([]));
        self.assertFalse(bool(()));
        self.assertFalse(bool({}));
        self.assertFalse(bool(0));
        
    def test_same_or_equal(self):
        # for small numbers, python's implementation may cache them
        # so different names may point to same object
        smallValue1 = 100
        smallValue2 = 100
        self.assertTrue(smallValue1 is smallValue2)
        
        # for large numbers, python will not cache these objects
        largeValue1 = 100000000000000000
        largeValue2 = 100000000000000000
        # !!!!!!!!!!!!!!!!!! though if execute the same codes in intepreter, then "is" will return false
        # !!!!!!!!!!!!!!!!!! but if execute the codes in script, "is" returns true
        # !!!!!!!!!!!!!!!!!! this actually reflects what is said in python's documents, "for immutable type, it really up to implementation"
        # self.assertFalse(largeValue1 is largeValue2)
        
    def test_reference_feature(self):
        """
        this testcase will show that in Python, even for numeric types, they are also reference type
        (in the conception of C#)
        numbers are also reference type, not like numerics in C#, which will store its value in stack
        in C#, "=" on numerics, will do copy
        but in Python, even for numeric type, "=" means name binding, means reference, not value copy
        """
        value1 = 10000000000000000
        value2 = value1
        # this assertation shows that "=" means two names bind to the same object
        # no other object will be allocated to store another 100
        self.assertTrue(value2 is value1)
        
        value2 = value1 + 0 # re-bind to another object
	self.assertEqual(value1,value2)
        self.assertFalse(value1 is value2)
        
    def test_check_immutable_feature(self):
        """
        this testcase demonstrates that "=" in Python doesn't mean fill value into a structure
        but mean "BIND" or "DIRECT",always means pointing to a new memory location
        
        we had better always think identifier in Python as Address or Pointer in C
        """
        number = 1
        oriid = id(number)
        
        number = 3
        newid = id(number) 
        
        self.assertTrue(oriid != newid)
        
    def test_round(self):
        expected = [3,3.1,3.14,3.142,3.1416]
        
        for ndig in xrange(len(expected)):
            self.assertAlmostEqual(expected[ndig],round(math.pi,ndig))
            
    def test_int_round_floor(self):
        """
        show the differnce between int, round and math.floor
        """
        # int just chops off the decimal part
        number1 = 3.8
        self.assertEqual(3,int(number1))
        # round always find the nearest integer but in float format
        self.assertAlmostEqual(4.0,round(number1))
        self.assertAlmostEqual(3.0,math.floor(number1))
        
        number2 = -1.1
        self.assertEqual(-1,int(number2))
        self.assertAlmostEqual(-1.0,round(number2))
        # floor always find the next smaller integer, so for negative number, it will return the integer even more negative
        self.assertAlmostEqual(-2.0,math.floor(number2))

class TestComplex(unittest.TestCase):
    def setUp(self):
        self._real = random.uniform(1,100) 
        self._imag = random.uniform(1,100) 
        self._cplx = complex(self._real,self._imag)
        
    def test_real_imaginary(self):
        self.assertAlmostEqual(self._real,self._cplx.real)
        self.assertAlmostEqual(self._imag,self._cplx.imag)
    
    def test_abs(self):
        expected_abs = math.sqrt(self._real ** 2 + self._imag ** 2)
        self.assertAlmostEqual(expected_abs,abs(self._cplx))
        
    def test_equal(self):
        self.assertEqual(complex(self._real),self._real)
        
    def test_phase(self):
        val = random.uniform(1,100)
        self.assertAlmostEqual(math.pi/4,cmath.phase( complex(val,val) ))
        
    def test_polar(self):
        """
        cmath.polar return (magnitude, phasor) of a complex number
        """
        magnitude,angle = cmath.polar(self._cplx)
        self.assertAlmostEqual(magnitude,abs(self._cplx))
        self.assertAlmostEqual(angle,cmath.phase(self._cplx))
        
    def test_rect(self):
        mag = random.uniform(1,100)
        ang = random.uniform(1,100) # angle can exeeds the rrange of [-pi,pi]
        cpx = cmath.rect(mag,ang)
        
        self.assertAlmostEqual(mag * math.cos(ang),cpx.real)
        self.assertAlmostEqual(mag * math.sin(ang),cpx.imag)

class TestMath(unittest.TestCase):
    def test_divmod(self):
        x = random.randint(1,100)
        y = random.randint(1,100)
        
        quo,resid = divmod(x,y)
        
        self.assertEqual(x/y,quo)
        self.assertEqual(x%y,resid)
        
    def test_floor_trunc_round(self):
        # ------------ floor always returns the next lower integer
        self.assertEqual(2,math.floor(2.9))
        self.assertEqual(-3,math.floor(-2.9))
        
        # ------------ trunc just truncates digital parts and remain the integer part
        self.assertEqual(2,math.trunc(2.9))
        self.assertEqual(-2,math.trunc(-2.9))
        
        # ------------ int just works like the trunc, just truncate other than round
        self.assertEqual(2,int(2.9))
        self.assertEqual(-2,int(-2.9))
        
        # ------------ round returns a float
        self.assertAlmostEqual(3.0,round(2.9))
        self.assertAlmostEqual(-3.0,round(-2.9))
        
        self.assertAlmostEqual(2.0,round(2.1))
        self.assertAlmostEqual(-2.0,round(-2.1))
        
        # ------------ round returns a float
        self.assertAlmostEqual(3.0,round(2.93))
        self.assertAlmostEqual(2.9,round(2.93,1))
        
if __name__ == "__main__":
    unittest.main()
